﻿using Bricks.Injecting;
using System;
using System.Collections.Generic;

namespace Bricks
{

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TKernel"></typeparam>
    /// <typeparam name="TFactory"></typeparam>
    public class UnitOfWorkFoundation<TKernel, TFactory> : DisposableLazyFacadeBase<TKernel>, IUnitOfWork<TKernel> where TFactory : IFactory<TKernel>
    {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="factory"></param>
        public UnitOfWorkFoundation(TFactory factory) :
            base(new Lazy<TKernel>(factory.Create, true))
        {
        }

        /// <summary>
        /// 
        /// </summary>
        [ThreadStatic]
        private static IDictionary<string, IUnitOfWork<TKernel>> _unitOfWorks;

        /// <summary>
        /// 
        /// </summary>
        private static IDictionary<string, IUnitOfWork<TKernel>> UnitOfWorks
        {
            get
            {
                if (_unitOfWorks == null)
                {
                    _unitOfWorks = new Dictionary<string, IUnitOfWork<TKernel>>();
                }

                return _unitOfWorks;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static bool IsAlive()
        {
            return IsAlive(string.Empty);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool IsAlive(string key)
        {
            return UnitOfWorks.ContainsKey(key);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        public static void Run(Action<TKernel> action)
        {
            Run<bool>(
                kernel =>
                {
                    action.Invoke(kernel);

                    return true;
                });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        public static void Run(Action<Func<TKernel>> action)
        {
            Run<bool>(
                kernelAccessor =>
                {
                    action.Invoke(kernelAccessor);

                    return true;
                });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="action"></param>
        public static void Run(string key, Action<TKernel> action)
        {
            Run<bool>(key,
                kernel =>
                {
                    action.Invoke(kernel);

                    return true;
                });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="action"></param>
        public static void Run(string key, Action<Func<TKernel>> action)
        {
            Run<bool>(key,
                kernelAccessor =>
                {
                    action.Invoke(kernelAccessor);

                    return true;
                });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="action"></param>
        /// <returns></returns>
        public static TResult Run<TResult>(Func<TKernel, TResult> action)
        {
            return Run<TResult>(string.Empty, action);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="action"></param>
        /// <returns></returns>
        public static TResult Run<TResult>(Func<Func<TKernel>, TResult> action)
        {
            return Run<TResult>(string.Empty, action);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="key"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static TResult Run<TResult>(string key, Func<TKernel, TResult> action)
        {
            return Run<TResult>(key, unitOfWork => unitOfWork.Execute<TResult>(action));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="key"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static TResult Run<TResult>(string key, Func<Func<TKernel>, TResult> action)
        {
            return Run<TResult>(key, unitOfWork => unitOfWork.Execute<TResult>(action));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="key"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        private static TResult Run<TResult>(string key, Func<IUnitOfWork<TKernel>, TResult> action)
        {
            IUnitOfWork<TKernel> unitOfWork;

            if (UnitOfWorks.TryGetValue(key, out unitOfWork))
            {
                return action.Invoke(unitOfWork);
            }
            else
            {
                using (unitOfWork = new UnitOfWorkFoundation<TKernel, TFactory>(ServiceLocator.Current.Resolve<TFactory>(key)))
                {
                    UnitOfWorks.Add(key, unitOfWork);

                    try
                    {
                        return action.Invoke(unitOfWork);
                    }
                    catch
                    {
                        throw;
                    }
                    finally
                    {
                        UnitOfWorks.Remove(key);
                    }
                }
            }
        }
    }
}
